Вивчіть основні концепції та передові методи рендерингу тіней в реальному часі у WebGL. Цей посібник охоплює shadow mapping, PCF, CSM та рішення поширених артефактів.
WebGL Shadow Mapping: Всеосяжний посібник з рендерингу в реальному часі
У світі 3D комп'ютерної графіки мало що сприяє реалізму та зануренню більше, ніж тіні. Вони надають важливі візуальні підказки про просторові взаємозв'язки між об'єктами, розташування джерел світла та загальну геометрію сцени. Без тіней 3D світи можуть здаватися плоскими, роз'єднаними та штучними. Для веб-додатків на основі 3D, що працюють на WebGL, реалізація високоякісних тіней у реальному часі є відмінною рисою досвіду професійного рівня. Цей посібник надає поглиблений огляд найфундаментальнішої та найширше використовуваної техніки для досягнення цього: Shadow Mapping.
Незалежно від того, чи ви досвідчений графічний програміст, чи веб-розробник, який вирушає у третій вимір, ця стаття озброїть вас знаннями, щоб розуміти, реалізовувати та усувати несправності тіней у реальному часі у ваших проектах WebGL. Ми здійснимо подорож від основної теорії до практичних деталей реалізації, досліджуючи загальні підводні камені та передові методи, які використовуються в сучасних графічних двигунах.
Глава 1: Основи Shadow Mapping
В основі shadow mapping — це хитра та елегантна техніка, яка визначає, чи знаходиться точка на сцені в тіні, задаючи просте питання: «Чи можна побачити цю точку джерелом світла?» Якщо відповідь ні, це означає, що щось блокує світло, і точка має бути в тіні. Щоб програмно відповісти на це питання, ми використовуємо двопрохідний підхід рендерингу.
Що таке Shadow Mapping? Основна концепція
Вся техніка обертається навколо рендерингу сцени двічі, кожен раз з іншої точки зору:
- Прохід 1: Прохід глибини (Перспектива світла). Спочатку ми рендеримо всю сцену з точного положення та орієнтації джерела світла. Однак у цьому проході нас не цікавлять кольори чи текстури. Єдина інформація, яка нам потрібна, — це глибина. Для кожного відрендереного об'єкта ми записуємо його відстань від джерела світла. Цей набір значень глибини зберігається у спеціальній текстурі під назвою shadow map або карта глибини. Кожен піксель на цій карті представляє відстань до найближчого об'єкта з точки зору світла в певному напрямку.
- Прохід 2: Прохід сцени (Перспектива камери). Далі ми рендеримо сцену, як зазвичай, з перспективи основної камери. Але для кожного окремого пікселя, що малюється, ми виконуємо додатковий розрахунок. Ми визначаємо положення цього пікселя в 3D просторі, а потім запитуємо: «Наскільки далеко ця точка від джерела світла?» Потім ми порівнюємо цю відстань зі значенням, збереженим у нашій shadow map (з Проходу 1) у відповідному місці.
Логіка проста:
- Якщо поточна відстань пікселя від світла більша за відстань, збережену у shadow map, це означає, що існує інший об'єкт ближче до світла вздовж тієї ж лінії зору. Отже, поточний піксель знаходиться в тіні.
- Якщо відстань пікселя менша або дорівнює відстані у shadow map, це означає, що ніщо не блокує його, і піксель повністю освітлений.
Налаштування сцени
Щоб реалізувати shadow mapping у WebGL, вам знадобиться кілька ключових компонентів:
- Джерело світла: Це може бути спрямоване світло (наприклад, сонце), точкове світло (наприклад, лампочка) або прожектор. Тип світла визначатиме вид матриці проекції, що використовується під час проходу глибини.
- Об'єкт буфера кадрів (FBO): WebGL зазвичай рендериться до буфера кадрів за замовчуванням екрана. Щоб створити нашу shadow map, нам потрібна позаекранна ціль рендерингу. FBO дозволяє нам рендерити в текстуру замість екрана. Наш FBO буде налаштований із прив'язкою текстури глибини.
- Два набори шейдерів: Вам знадобиться одна програма шейдера для проходу глибини (дуже проста) та інша для фінального проходу сцени (яка міститиме логіку розрахунку тіні).
- Матриці: Вам знадобляться стандартні матриці моделі, перегляду та проекції для камери. Дуже важливо, вам також знадобиться матриця перегляду та проекції для джерела світла, які часто об'єднуються в єдину «матрицю світлового простору».
Глава 2: Детальний двопрохідний конвеєр рендерингу
Розберемо два проходи рендерингу крок за кроком, зосереджуючись на ролях матриць і шейдерів.
Прохід 1: Прохід глибини (З перспективи світла)
Мета цього проходу — заповнити нашу текстуру глибини. Ось як це працює:
- Прив'яжіть FBO: Перед малюванням ви наказуєте WebGL рендерити до вашого власного FBO замість полотна.
- Налаштуйте вікно перегляду: Встановіть розміри вікна перегляду відповідно до розміру вашої текстури shadow map (наприклад, 1024x1024 пікселів).
- Очистіть буфер глибини: Переконайтеся, що буфер глибини FBO очищено перед рендерингом.
- Створіть матриці світла:
- Матриця перегляду світла: Ця матриця перетворює світ у точку зору світла. Для спрямованого світла це зазвичай створюється за допомогою функції `lookAt`, де «око» — це положення світла, а «ціль» — напрямок, на який воно вказує.
- Матриця проекції світла: Для спрямованого світла, яке має паралельні промені, використовується ортогональна проекція. Для точкових джерел світла або прожекторів використовується перспективна проекція. Ця матриця визначає об'єм у просторі (коробка або усічений конус), який відкидатиме тіні.
- Використовуйте програму шейдера глибини: Це мінімальний шейдер. Єдине завдання вершинного шейдера — помножити положення вершини на матриці перегляду та проекції світла. Фрагментний шейдер ще простіший: він просто записує значення глибини фрагмента (його z-координату) у текстуру глибини. У сучасному WebGL вам часто навіть не потрібен власний фрагментний шейдер, оскільки FBO можна налаштувати для автоматичного захоплення буфера глибини.
- Рендерінг сцени: Намалюйте всі об'єкти, що відкидають тіні, на вашій сцені. FBO тепер містить нашу завершену shadow map.
Прохід 2: Прохід сцени (З перспективи камери)
Тепер ми рендеримо остаточне зображення, використовуючи shadow map, яку ми щойно створили, щоб визначити тіні.
- Відв'яжіть FBO: Перейдіть назад до рендерингу до буфера кадрів полотна за замовчуванням.
- Налаштуйте вікно перегляду: Встановіть вікно перегляду назад до розмірів полотна.
- Очистіть екран: Очистіть колірні буфери та буфери глибини полотна.
- Використовуйте програму шейдера сцени: Тут відбувається магія. Цей шейдер складніший.
- Вершинний шейдер: Цей шейдер повинен робити дві речі. По-перше, він обчислює остаточне положення вершини, використовуючи матриці моделі, перегляду та проекції камери, як зазвичай. По-друге, він повинен також обчислити положення вершини з перспективи світла, використовуючи матрицю світлового простору з Проходу 1. Ця друга координата передається фрагментному шейдеру як змінювана.
- Фрагментний шейдер: Це ядро логіки тіні. Для кожного фрагмента:
- Отримайте інтерпольоване положення у світловому просторі з вершинного шейдера.
- Виконайте перспективний поділ над цією координатою (поділіть x, y, z на w). Це перетворює її в Нормалізовані координати пристрою (NDC) у діапазоні від -1 до 1.
- Перетворіть NDC у текстурні координати (які знаходяться в діапазоні від 0 до 1), щоб ми могли вибіркувати нашу shadow map. Це проста операція масштабування та зміщення: `texCoord = ndc * 0.5 + 0.5;`.
- Використовуйте ці текстурні координати для вибірки текстури shadow map, створеної у Проході 1. Це дає нам `depthFromShadowMap`.
- Поточна глибина фрагмента з точки зору світла — це його z-компонент з перетвореної координати світлового простору. Назвемо її `currentDepth`.
- Порівняйте глибини: Якщо `currentDepth > depthFromShadowMap`, фрагмент знаходиться в тіні. Нам потрібно буде додати невелике зміщення до цієї перевірки, щоб уникнути артефакту під назвою «акне тіні», про який ми поговоримо далі.
- На основі порівняння визначте фактор тіні (наприклад, 1,0 для освітленого, 0,3 для затіненого).
- Застосуйте цей коефіцієнт тіні до остаточного розрахунку кольору (наприклад, помножте компоненти навколишнього та дифузного освітлення на коефіцієнт тіні).
- Рендерінг сцени: Намалюйте всі об'єкти на сцені.
Глава 3: Поширені проблеми та рішення
Реалізація базового shadow mapping швидко виявить кілька поширених візуальних артефактів. Розуміння та виправлення їх має вирішальне значення для досягнення високоякісних результатів.
Акне тіні (Артефакти самозатінення)
Проблема: Ви можете бачити дивні, неправильні візерунки темних ліній або візерунків, схожих на муар, на поверхнях, які мають бути повністю освітлені. Це називається «акне тіні». Це відбувається тому, що значення глибини, збережене у shadow map, і значення глибини, обчислене під час проходу сцени, призначені для тієї ж поверхні. Через неточності з плаваючою комою та обмежену роздільну здатність shadow map, крихітні помилки можуть призвести до того, що фрагмент неправильно визначає, що він знаходиться позаду себе, що призводить до самозатінення.
Рішення: Зміщення глибини. Найпростіше рішення — ввести невелике зміщення до `currentDepth` перед порівнянням. Змушуючи фрагмент здаватися трохи ближчим до світла, ніж він є насправді, ми виштовхуємо його «з» власної тіні.
float shadow = currentDepth > depthFromShadowMap + bias ? 0.3 : 1.0;
Знайти правильне значення зміщення — це делікатне балансування. Занадто мало, і акне залишається. Занадто багато, і ви отримуєте наступну проблему.
Peter Panning
Проблема: Цей артефакт, названий на честь персонажа, який міг літати та втратив свою тінь, проявляється як видимий проміжок між об'єктом та його тінню. Він змушує об'єкти здаватися плаваючими або від'єднаними від поверхонь, на яких вони повинні лежати. Це прямий результат використання зміщення глибини, яке є занадто великим.
Рішення: Зміщення глибини масштабування нахилу. Більш надійним рішенням, ніж постійне зміщення, є зробити зміщення залежним від крутизни поверхні відносно світла. Крутіші багатокутники більш схильні до акне і потребують більшого зміщення. Більш плоскі багатокутники потребують меншого зміщення. Більшість графічних API, включаючи WebGL, забезпечують функціональність для автоматичного застосування такого роду зміщення під час проходу глибини, що зазвичай краще, ніж ручне зміщення у фрагментному шейдері.
Перспективне згладжування (зубчасті краї)
Проблема: Краї ваших тіней виглядають блоковими, зубчастими та піксельними. Це форма згладжування. Це відбувається тому, що роздільна здатність shadow map обмежена. Окремий піксель (або тексель) у shadow map може покривати велику площу на поверхні в остаточній сцені, особливо для поверхонь поблизу камери або тих, що видно під ковзним кутом. Ця невідповідність у роздільній здатності викликає характерний блоковий вигляд.
Рішення: Збільшення роздільної здатності shadow map (наприклад, з 1024x1024 до 4096x4096) може допомогти, але це потребує значних витрат пам'яті та продуктивності та не повністю вирішує основну проблему. Справжні рішення лежать у більш передових методах.
Глава 4: Передові методи Shadow Mapping
Основний shadow mapping забезпечує основу, але професійні програми використовують більш складні алгоритми для подолання його обмежень, зокрема згладжування.
Фільтрація Percentage-Closer (PCF)
PCF — найпоширеніша техніка для пом'якшення країв тіні та зменшення згладжування. Замість того, щоб взяти один зразок з shadow map і прийняти двійкове (у тіні або не в тіні) рішення, PCF бере кілька зразків з області навколо цільової координати.
Концепція: Для кожного фрагмента ми вибіркуємо shadow map не один раз, а за сітчастим візерунком (наприклад, 3x3 або 5x5) навколо спроектованої текстурної координати фрагмента. Для кожного з цих зразків ми виконуємо порівняння глибини. Остаточне значення тіні — це середнє всіх цих порівнянь. Наприклад, якщо 4 з 9 зразків знаходяться в тіні, фрагмент буде 4/9 затемнений, що призведе до плавного півотіння (м'який край тіні).
Реалізація: Це робиться повністю у фрагментному шейдері. Він передбачає цикл, який повторює невелике ядро, вибірку shadow map на кожному зміщенні та накопичення результатів. WebGL 2 пропонує апаратну підтримку (`texture` з `sampler2DShadow`), яка може виконувати порівняння та фільтрацію більш ефективно.
Перевага: Різко покращує якість тіні, замінюючи жорсткі, згладжені краї на плавні, м'які.
Вартість: Продуктивність знижується зі збільшенням кількості зразків, взятих на фрагмент.
Каскадні shadow maps (CSM)
CSM — це галузевий стандарт рішення для рендерингу тіней від одного спрямованого джерела світла (наприклад, сонця) над дуже великою сценою. Він безпосередньо вирішує проблему перспективного згладжування.
Концепція: Основна ідея полягає в тому, що об'єкти поблизу камери потребують набагато вищої роздільної здатності тіні, ніж об'єкти, що знаходяться далеко. CSM поділяє камеру перегляду усіченого конуса на кілька секцій, або «каскадів», уздовж його глибини. Окрему високоякісну shadow map потім рендериться для кожного каскаду. Каскад, найближчий до камери, покриває невелику площу світового простору та, таким чином, має дуже високу ефективну роздільну здатність. Каскади, що знаходяться далі, покривають прогресивно більші області з тим самим розміром текстури, що прийнятно, оскільки ці деталі менш помітні для гравця.
Реалізація: Це значно складніше.
- На процесорі розділіть усічений конус камери на 2–4 каскади.
- Для кожного каскаду обчисліть щільно прилеглу ортогональну матрицю проекції для світла, яка ідеально охоплює цю частину усіченого конуса.
- У циклі рендерингу виконуйте прохід глибини кілька разів — один раз для кожного каскаду, рендеринг у іншу shadow map (або область атласу текстур).
- У фрагментному шейдері остаточного проходу сцени визначте, до якого каскаду належить поточний фрагмент, на основі його відстані від камери.
- Вибіркуйте shadow map відповідного каскаду, щоб обчислити тінь.
Перевага: Забезпечує стабільно високу роздільну здатність тіней на великих відстанях, що робить його ідеальним для зовнішнього середовища.
Карти тіньової дисперсії (VSM)
VSM — ще одна техніка для створення м'яких тіней, але вона використовує інший підхід, ніж PCF.
Концепція: Замість того, щоб зберігати лише глибину у shadow map, VSM зберігає два значення: глибину (перший момент) і глибину в квадраті (другий момент). Ці два значення дозволяють нам обчислити дисперсію розподілу глибини. Використовуючи математичний інструмент, який називається нерівність Чебишева, ми можемо оцінити ймовірність того, що фрагмент знаходиться в тіні. Ключова перевага полягає в тому, що текстуру VSM можна розмити за допомогою стандартної апаратно-прискореної лінійної фільтрації та mipmapping, що математично недійсно для стандартної карти глибини. Це дозволяє створювати дуже великі, м'які та плавні тіні з фіксованою вартістю продуктивності.
Недолік: Основною слабкістю VSM є «витікання світла», коли світло може здаватися, що проникає через об'єкти в ситуаціях із перекриттям обмежувачів, оскільки статистичне наближення може зруйнуватися.
Глава 5: Практичні поради щодо реалізації та продуктивності
Вибір роздільної здатності вашої Shadow Map
Роздільна здатність вашої shadow map — це прямий компроміс між якістю та продуктивністю. Більша текстура забезпечує чіткіші тіні, але споживає більше відеопам'яті та займає більше часу на рендеринг і вибірку. Поширені розміри включають:
- 1024x1024: Хороша базова лінія для багатьох програм.
- 2048x2048: Пропонує помітне покращення якості для настільних програм.
- 4096x4096: Висока якість, часто використовується для основних ресурсів або в двигунах з надійним відкиданням.
Оптимізація усіченого конуса світла
Щоб отримати максимальну віддачу від кожного пікселя у вашій shadow map, важливо, щоб об'єм проекції світла (його ортогональний бокс або усічений конус перспективи) був максимально щільно припасований до елементів сцени, яким потрібні тіні. Для спрямованого світла це означає, що його ортогональна проекція має вміщувати лише видиму частину усіченого конуса камери. Будь-який зайвий простір у shadow map — це марна роздільна здатність.
Розширення та версії WebGL
WebGL 1 проти WebGL 2: Хоча shadow mapping можливий у WebGL 1, це набагато простіше та ефективніше у WebGL 2. WebGL 1 потребує розширення `WEBGL_depth_texture`, щоб створити текстуру глибини. WebGL 2 має цю функціональність вбудовану. Крім того, WebGL 2 надає доступ до семплерів тіней (`sampler2DShadow`), які можуть виконувати апаратне прискорення PCF, що забезпечує значне підвищення продуктивності над ручними циклами PCF у шейдері.
Відлагодження тіней
Тіні можуть бути надзвичайно важкими для відлагодження. Найбільш корисним методом є візуалізація shadow map. Тимчасово змініть свою програму, щоб рендерити текстуру глибини з певного джерела світла безпосередньо на чотирикутник на екрані. Це дозволяє вам бачити саме те, що «бачить» світло. Це може негайно виявити проблеми з матрицями світла, відкиданням усіченого конуса або рендерингом об'єктів під час проходу глибини.
Висновок
Рендеринг тіней в реальному часі є наріжним каменем сучасної 3D графіки, перетворюючи плоскі, безжиттєві сцени на правдоподібні та динамічні світи. Хоча концепція рендерингу з перспективи світла проста, досягнення високоякісних результатів без артефактів вимагає глибокого розуміння основної механіки, від двопрохідного конвеєра до нюансів зміщення глибини та згладжування.
Почавши з базової реалізації, ви можете поступово вирішувати поширені артефакти, такі як акне тіні та зубчасті краї. Звідти ви можете підняти свої візуальні ефекти за допомогою передових методів, таких як PCF для м'яких тіней або Каскадні shadow maps для великомасштабного середовища. Подорож у рендеринг тіней — чудовий приклад поєднання мистецтва та науки, що робить комп'ютерну графіку такою захоплюючою. Ми рекомендуємо вам експериментувати з цими методами, розширювати їхні межі та додавати новий рівень реалізму у ваші проекти WebGL.